#!/data/Software/mydan/perl/bin/perl -I/data/Software/mydan/JOB/lib -I/data/Software/mydan/JOB/private/lib
use strict;
use warnings;

use FindBin qw( $RealBin );
use MIME::Base64;
use Data::Dumper;
use File::Basename;
use Sys::Hostname;

use POSIX;
use MYDan::Util::OptConf;
use MYDan::Agent::Client;
use MYDan::VSSH::Print;
use Digest::MD5;
use Code;
use Logs;
use variable;
use YAML::XS;

$| ++;

=head1 SYNOPSIS

    db => $mysql,
    uuid => 'uuid',
    taskuuid => 'uuid', # Used to make a pause
    projectid => 1, # To determine the file system path

    fromjob => jobuuid or undef , It is used to determine the subtask, Rely on the taskuuid . and the jobuuid use for variable replace
    variable => hash

    logs => 日志对象

    head => 0 or 1
    tail => 0 or 1

=cut

return sub
{
    my %param = @_;
    my ( $db, $uuid, $taskuuid, $projectid, $fromjob, $variable, $logs, $head, $tail ) 
        = @param{qw(db uuid taskuuid projectid fromjob variable logs head tail )};

    delete $ENV{MYDanExtractFile};
    delete $ENV{MYDanExtractFileAim};

    $logs = Logs->new( 'code.plugin_scp' ) unless $logs;
    my $vv = Code->new( 'vv' );

    if( $taskuuid )
    {
        open STDOUT, '>>', "$RealBin/../logs/task/${taskuuid}${uuid}scp" 
            or $logs->die( "Can't open 'logs/task/${taskuuid}${uuid}scp': $!" );
        open (STDERR, ">&STDOUT") || $logs->die( "open STDERR failed: $uuid" );
    }

    $logs->die( "uuid format error" ) unless $uuid =~ /^[a-zA-Z0-9]+$/;
    my $x = $db->query( "select user,src,src_type,sp,dst,dst_type,dp,chown,chmod,timeout,pause,scp_delete,deployenv,action,batches from plugin_scp where uuid='$uuid'" );
    $logs->die( "get data error from db" ) unless defined $x && ref $x eq 'ARRAY';
    $logs->die( "plugin_scp uuid null: $uuid" ) unless @$x;

    my ( $user, $src, $src_type, $sp, $dst, $dst_type, $dp, $chown, $chmod, $timeout, $pause, $scp_delete, $deployenv, $action, $batches ) = @{$x->[0]};

    $logs->die( "taskuuid format error" ) if defined $taskuuid && $taskuuid !~ /^[a-zA-Z0-9]+$/;
    my ( $subtasktimems, $subtaskstarttime ) = ( time, POSIX::strftime( "%Y-%m-%d %H:%M:%S", localtime ) );

    my %env = Util::envinfo( qw( appname appkey ) );
    my $ua = LWP::UserAgent->new;
    $ua->default_header( %env );
    my $res = $ua->get( "http://api.jobx.open-c3.org/subtask/$projectid/$taskuuid/mystatus" );
    $logs->die( "get task status from jobx fail" ) unless $res->is_success;
    my $data = eval{JSON::from_json $res->content};
    $logs->die( "get task status from jobx fail:". $data->{info} || '' ) unless $data->{stat};

    print YAML::XS::Dump $data->{data};

    $data->{data}{action} ||= 'nofind';
    $data->{data}{deployenv} ||= 'nofind';
    $data->{data}{batches} ||= 0;
    $data->{data}{submitter} ||= 'nofind';

    print "=" x 35, "\n";
    print "check ...\n";
    print "expect deployenv: $deployenv; real deployenv:$data->{data}{deployenv}\n";
    return 'success' if $deployenv eq 'test' && $data->{data}{deployenv} ne 'test';
    return 'success' if $deployenv eq 'online' && $data->{data}{deployenv} ne 'online';
      
    print "expect action: $action; real action:$data->{data}{action}\n";
    return 'success' if $action eq 'deploy' && $data->{data}{action} ne 'deploy';
    return 'success' if $action eq 'rollback' && $data->{data}{action} ne 'rollback';
      
    print "expect batches: $batches real batches: $data->{data}{batches}\n";
    return 'success' if $batches eq 'firsttime' && $data->{data}{batches} ne 1;

    my ( @src, @dst );

    my $filename;
    my $ignore = 0;

    $logs->die( "src_type undef" ) unless defined $src_type;
    my ( @srccheck, @dstcheck );
    if( $src_type eq 'fileserver' )
    {
        $logs->die( "projectid not a number" ) unless $projectid && $projectid =~ /^\d+$/;

        eval { $sp = variable->new( variable => $variable, db => $db, jobuuid => $fromjob )->relpace( $sp ) if $sp =~ /\$/; };
        $logs->die( "relpace variable sp fail:$@" ) if $@;

        if ($sp =~ /^rollback:/ || $sp =~ /^comeback:/ || $sp =~ /^backup/) {
            $ignore = 1;
        }else{
            my $tx = $db->query( "select md5 from fileserver where projectid='$projectid' and name='$sp' and status='available'" );
            $logs->die( "get data error from db" ) unless defined $tx && ref $tx eq 'ARRAY';
            $logs->die( "fileserver null: $sp" ) unless @$tx;

            $filename = basename $sp;
            $sp = "$RealBin/../fileserver/$projectid/$tx->[0][0]";
        }
    }
    elsif( $src_type eq 'ci' )
    {
        $logs->die( "projectid not a number" ) unless $projectid && $projectid =~ /^\d+$/;

        if( $src eq 'default' && $fromjob )
        {
            my $jobname = $db->query( "select name from jobs where uuid='$fromjob'" );
            $logs->die( "get jobname error from db" ) unless defined $jobname && ref $jobname eq 'ARRAY';
            if( @$jobname > 0 )
            {
                if( $jobname->[0][0] =~ /^_ci_(\d+)_$/ )
                {
                    $src = $1;
                }
            }
        }

        eval { $src = variable->new( variable => $variable, db => $db, jobuuid => $fromjob )->relpace( $src ) if defined $src; };
        $logs->die( "relpace variable src fail:$@" ) if $@;

        my $tv; eval { $tv = variable->new( variable => $variable, db => $db, jobuuid => $fromjob )->get( 'version' ); };
        $logs->die( "get version from variable fail:$@" ) if $@;

        my $ua = LWP::UserAgent->new();
        $ua->agent('Mozilla/9 [en] (Centos; Linux)');

        my %env = eval{ Util::envinfo( qw( appkey appname ) ) };
        $logs->die( "get appkey appnane fail:$@" ) if $@;

        $ua->default_header( map{ $_ => $env{$_} }qw( appname appkey) );
        $ua->timeout( 10 );
        my $url = "http://api.ci.open-c3.org/versiondetail/$src/$tv";
        my $res = $ua->get( $url );
        $logs->die( "get ciinfo fail" ) unless $res->is_success;
        my $data = eval{JSON::from_json $res->content};
        $logs->die( "get ciinfo fail" ) unless $data->{stat};
        $logs->die( "get nofind the version on ci" ) unless $data->{data}{slave};
        $logs->die( "the version no success on ci" ) unless $data->{data}{status} eq 'success' || $data->{data}{status} eq 'running';

        #my $slave = $data->{data}{slave};
        #my $hostname = Sys::Hostname::hostname;
        #@src = $slave eq $hostname ? () : ( $slave );
        #
        #JOB and CI share storage
        #
        @src = ();
        $sp = "/data/glusterfs/ci_repo/$src/$tv";
    }
    elsif( $src_type eq 'builtin' )
    {
        @srccheck = @src = split /,/, $src;
        $logs->die( "src null" ) unless @src;
    }
    elsif( $src_type eq 'variable' )
    {
        eval { $src = variable->new( variable => $variable, db => $db, jobuuid => $fromjob )->relpace( $src ) if defined $src; };
        $logs->die( "relpace variable src fail:$@" ) if $@;
        @srccheck = @src = split /,/, $src;
        $logs->die( "src null" ) unless @src;
    }
    elsif( $src_type eq 'group' )
    {
        $logs->die( "src not a number" ) unless $src && $src =~ /^\d+$/;
        @srccheck = @src = Code->new( 'nodegroup' )->run( db => $db, id => $src );
        $logs->die( "src null" ) unless @src;
    }
    else
    {
        $logs->die( "src_type : $src_type unkown" );
    }

    
    $logs->die( "dst_type undef" ) unless defined $dst_type;
    if( $dst_type eq 'builtin' )
    {
        @dstcheck = @dst = split /,/, $dst;
    }
    elsif( $dst_type eq 'variable' )
    {
        eval { $dst = variable->new( variable => $variable, db => $db, jobuuid => $fromjob )->relpace( $dst ) if defined $dst; };
        $logs->die( "relpace variable dst fail:$@" ) if $@;
        @dstcheck = @dst = split /,/, $dst;
    }
    elsif( $dst_type eq 'group' )
    {
        $logs->die( "dst not a number" ) unless $dst && $dst =~ /^\d+$/;
        @dstcheck = @dst = Code->new( 'nodegroup' )->run( db => $db, id => $dst );
    }
    elsif( $dst_type eq 'fileserver' )
    {
        $dp = "/data/fileservertemp/". time. rand 1000000;
        @dst = ( '127.0.0.1' );
    }
    else
    {
        $logs->die( "dst_type : $dst_type unkown" );
    }

    $logs->die( "dst null" ) unless @dst;

    eval { $sp = variable->new( variable => $variable, db => $db, jobuuid => $fromjob )->relpace( $sp ) if defined $sp; };
    $logs->die( "relpace variable sp fail:$@" ) if $@;
    eval { $dp = variable->new( variable => $variable, db => $db, jobuuid => $fromjob )->relpace( $dp ) if defined $dp; };
    $logs->die( "relpace variable dp fail:$@" ) if $@;

    $dp = "$dp/" if $sp =~ m#/$#;
    $filename = basename $sp unless defined $filename;
    $dp = "$dp$filename" if $dp =~ m#/$# && $sp !~ m#/$#;


    if( $taskuuid && $fromjob )
    {
         my $nodecount = @dst;

         eval{ $db->execute( 
                 "update `subtask` set nodecount='$nodecount',starttime='$subtaskstarttime',runtime='0.00',pause='$pause',status='running'
                     where parent_uuid='$taskuuid' and subtask_type='scp' and uuid='$uuid'") };
         $logs->die( "set status to subtask fail:$@" ) if $@;
    }



    if( defined $projectid )
    {
        my $checkerror = eval{ Code->new( 'nodeinfo_check' )->run( db => $db, id => $projectid, node => [ @srccheck, @dstcheck ] ) };
        $logs->die( "nodeinfo_check code err:$@" ) if $@;
        $logs->die( "the node not on tree: $checkerror" ) if $checkerror;
    }

    my $_exit_;
    eval { $_exit_ = variable->new( variable => $variable, db => $db, jobuuid => $fromjob )->get( '_exit_' ) };
    $logs->die( "get variable _exit_ fail:$@" ) if $@;


    $timeout = 60 unless $timeout && $timeout =~ /^\d+$/;
	$MYDan::Util::OptConf::THIS = 'grsync';

    use Sys::Hostname;
    use MYDan::Node;
    #use MYDan::Agent::Grsync::V12;
    #use MYDan::Agent::Grsync::V3;
    #use MYDan::Agent::Grsync::V4;
    use MYDan::Agent::GrsyncM;
    use MYDan::Util::OptConf;
    $| ++;

    @MYDan::Util::OptConf::CONF = qw( pass_through no_ignore_case );
    my $option = MYDan::Util::OptConf->load();
    my %o = $option->dump();

#    my %o = $option->set( retry => 2, timeout => 300, gave => 3 )
#        ->get( qw( src=s dst=s sp=s dp=s timeout=i max=i retry=i nice=i user=s sudo=s gave=i chown=s chmod=s cc 1 2 3 ) )
#        ->dump();

    $o{4} = 1 if $dp =~ /^\/{3}/;
    $o{retry} = 2;
    $o{timeout} = $timeout;
    $o{gave} = 3;
    $o{sp} = $sp;
    $o{dp} = $dp;
    $o{chown} = $chown if defined $chown && $chown =~ /[a-zA-Z0-9]/;
    $o{chmod} = $chmod if defined $chmod && $chmod =~ /[a-zA-Z0-9]/;
    $o{delete} = 1 if $scp_delete;

    $o{delete $o{ProtocolVersion}} = 1 unless $o{ProtocolVersion} && $o{ProtocolVersion} =~ /^\d$/ && grep{ $o{$_} } 1 .. 3;
    $o{user} = 'jobsys' unless $o{user};
    my $range = MYDan::Node->new( $option->dump( 'range' ) );

    $logs->say("scp delete is : $scp_delete ");
    $logs->say(YAML::XS::Dump \%o);

    if ($filename =~ /^rollback:/ || $filename =~ /^comeback:/  || $filename =~ /^backup/) {
        $ignore = 1;
    }
    my $subtaskstatus = $ignore ? 'ignore' : 'success'; #runnigs,fail,success,decision,ignore

    print YAML::XS::Dump \%o;

    while( ! $ignore )
    {
        my %sync = (
            src => \@src, dst => \@dst,
            agent => +{ $option->dump( 'agent' ) }
        );
    
        #my $grsync = $o{4} ? MYDan::Agent::Grsync::V4->new( %sync ) :
        #             $o{3} ? MYDan::Agent::Grsync::V3->new( %sync ) :
        #                     MYDan::Agent::Grsync::V12->new( %sync );
        my @failed = MYDan::Agent::GrsyncM->new( opt => \%o, sync => \%sync )->run();
    #    exit 0 unless my @failed = $grsync->run( %o )->failed();
    #    die $range->load( \@failed )->dump . ": failed.\n\n";
    
        #my @failed = $grsync->run( %o )->failed();
        #die $range->load( \@failed )->dump . ": failed.\n\n" if @failed;

        if( ! @failed && $dst_type eq 'fileserver' )
        {
            open my $fh, "<$dp" or $logs->die( "open file fail" );
            my $md5 = Digest::MD5->new()->addfile( $fh )->hexdigest;
            close $fh;

            my $path = "$RealBin/../fileserver/$projectid";
            mkdir $path unless -d $path;

            my $size = ( stat $dp )[7];
            $logs->die( "rename fail" ) if system "mv '$dp' '$path/$md5'";

            my $time = POSIX::strftime( "%Y-%m-%d %H:%M:%S", localtime );

            eval{
              $db->execute(
                "replace into fileserver (`projectid`,`name`,`size`,`md5`,`create_user`,`create_time`,`edit_user`,`edit_time`,`status`)
                    values( '$projectid', '$filename','$size', '$md5', '$user','$time', '$user', '$time','available' )")};

            $logs->die( "update db fail:$@" ) if $@;
        }

        if( @failed )
        {
            print "failed:\n";
            map{ print "  $_\n"; }@failed;
        }

        unless( $taskuuid && $fromjob )
        {
            $subtaskstatus = @failed ? 'fail' : 'success'; 
            last;
        }

        last unless @failed;

        eval{ $db->execute( "update `subtask` set status='decision' where parent_uuid='$taskuuid' and subtask_type='scp' and uuid='$uuid'") };
        eval{ $db->execute( "update `task` set status='waiting',notify='0' where uuid='$taskuuid'") };

        my $tmpstatus;
        if( $_exit_ && ( $_exit_ eq '1' || $_exit_ eq 'scp' || $_exit_ eq 'true' ) )
        {
            $tmpstatus = 'fail';
            eval{ $db->execute( "update task set reason='stop by sys._exit_' where uuid='$taskuuid' and reason is null" ) };
            $logs->die( "update reason fail" ) if $@;

        }
        else
        {
            while(1)
            {
                $x = $db->query( "select status from subtask where parent_uuid='$taskuuid' and subtask_type='scp' and uuid='$uuid'" );
                $logs->die( "get data error from db" ) unless defined $x && ref $x eq 'ARRAY';
                $logs->die( "subtask status null: parent_uuid='$taskuuid' and subtask_type='cmd' and uuid='$uuid'" ) unless @$x;
                if( grep{ $x->[0][0] eq $_ }qw( fail ignore running ) )
                {
                    $tmpstatus = $x->[0][0];
                    last;
                }
                sleep 3;
            }
        }

        eval{ $db->execute( "update `task` set status='running',notify='0' where uuid='$taskuuid'") };
        if( $tmpstatus eq 'running' )
        {
            my %failed = map{ $_ => 1 }@failed;
            push( @src, grep{ ! $failed{$_} }@dst ) if $dp eq $sp;
            @dst = @failed;
            next;
        }
        $subtaskstatus = $tmpstatus;
        last;
    }

    if( $taskuuid && $fromjob )
    {
         my $subtaskruntime = sprintf "%0.3f", time - $subtasktimems;
         my $subtaskfinishtime = POSIX::strftime( "%Y-%m-%d %H:%M:%S", localtime );
         eval{ $db->execute( "update `subtask` set runtime='$subtaskruntime',finishtime='$subtaskfinishtime',status='$subtaskstatus' 
                 where parent_uuid='$taskuuid' and subtask_type='scp' and uuid='$uuid'") };
    }

    return $subtaskstatus unless $taskuuid && $fromjob && $pause;

    Code->new( 'pause' )->run( db => $db,  taskuuid => $taskuuid, subtaskuuid => $uuid, subtasktype => 'scp' );

    return $subtaskstatus;
}
